/*
 * Copyright (c) 2023-2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ecmascript/mem/machine_code.h"
#include "ecmascript/compiler/aot_file/func_entry_des.h"
#include "ecmascript/compiler/binary_section.h"
#include "ecmascript/jit/jit.h"
#include "ecmascript/log_wrapper.h"
#include "machine_code.h"
#include <cstdint>
#if ECMASCRIPT_ENABLE_CAST_CHECK
#include "ecmascript/js_tagged_value-inl.h"
#endif

extern struct jit_descriptor __jit_debug_descriptor;

namespace panda::ecmascript {

static bool SetPageProtect(uint8_t *textStart, size_t dataSize)
{
    if (!Jit::GetInstance()->IsEnableJitFort()) {
        constexpr size_t pageSize = 4096;
        uintptr_t startPage = AlignDown(reinterpret_cast<uintptr_t>(textStart), pageSize);
        uintptr_t endPage = AlignUp(reinterpret_cast<uintptr_t>(textStart) + dataSize, pageSize);
        size_t protSize = endPage - startPage;
        return PageProtect(reinterpret_cast<void*>(startPage), protSize, PAGE_PROT_EXEC_READWRITE);
    }
    return true;
}

static int MachineCodeCopyToCache([[maybe_unused]] const MachineCodeDesc &desc, [[maybe_unused]] uint8_t *pText)
{
#ifndef JIT_ENABLE_CODE_SIGN
    if (memcpy_s(pText, desc.codeSizeAlign, // LCOV_EXCL_BR_LINE
        reinterpret_cast<uint8_t*>(desc.codeAddr),
        desc.codeSize) != EOK) {
        LOG_JIT(ERROR) << "memcpy failed in CopyToCache";
        return false;
    }
#endif
    return true;
}

bool MachineCode::SetText(const MachineCodeDesc &desc)
{
    uint8_t *textStart = reinterpret_cast<uint8_t*>(GetText());
    uint8_t *pText = textStart;
    if (desc.rodataSizeBeforeTextAlign != 0) {
        if (memcpy_s(pText, desc.rodataSizeBeforeTextAlign, // LCOV_EXCL_BR_LINE
            reinterpret_cast<uint8_t*>(desc.rodataAddrBeforeText),
            desc.rodataSizeBeforeText) != EOK) {
            LOG_JIT(ERROR) << "memcpy fail in copy fast jit code";
            return false;
        }
        pText += desc.rodataSizeBeforeTextAlign;
    }
    if (!Jit::GetInstance()->IsEnableJitFort() || !Jit::GetInstance()->IsEnableAsyncCopyToFort() ||
        !desc.isAsyncCompileMode) {
        if (MachineCodeCopyToCache(desc, pText) == false) {
            return false;
        }
    }
    pText += desc.codeSizeAlign;
    if (desc.rodataSizeAfterTextAlign != 0) {
        if (memcpy_s(pText, desc.rodataSizeAfterTextAlign, // LCOV_EXCL_BR_LINE
            reinterpret_cast<uint8_t*>(desc.rodataAddrAfterText),
            desc.rodataSizeAfterText) != EOK) {
            LOG_JIT(ERROR) << "memcpy fail in copy fast jit code";
            return false;
        }
    }
    return true;
}

bool MachineCode::SetNonText(const MachineCodeDesc &desc, EntityId methodId)
{
    uint8_t *textStart = reinterpret_cast<uint8_t*>(GetText());
    uint8_t *stackmapAddr = GetStackMapOrOffsetTableAddress();
    if (memcpy_s(stackmapAddr, desc.stackMapOrOffsetTableSize, // LCOV_EXCL_BR_LINE
                 reinterpret_cast<uint8_t*>(desc.stackMapOrOffsetTableAddr),
                 desc.stackMapOrOffsetTableSize) != EOK) {
        LOG_JIT(ERROR) << "memcpy fail in copy fast jit stackmap";
        return false;
    }

    FuncEntryDes *funcEntry = reinterpret_cast<FuncEntryDes*>(desc.funcEntryDesAddr);
    if (!funcEntry) {
        LOG_JIT(ERROR) << "funcEntry is null.";
        return false;
    }
    uint32_t cnt = desc.funcEntryDesSize / sizeof(FuncEntryDes);
    ASSERT(cnt <= 2); // 2: jsfunction + deoptimize stub
    for (uint32_t i = 0; i < cnt; i++) {
        if (methodId == EntityId(funcEntry->indexInKindOrMethodId_)) {
            uint64_t codeAddr = funcEntry->codeAddr_ +
                                static_cast<uint64_t>(reinterpret_cast<uintptr_t>(textStart));
            SetFuncAddr(codeAddr);
            break;
        }
        funcEntry++;
    }

    SetIsFastCall(funcEntry->isFastCall_);
    SetFpDeltaPrevFrameSp(funcEntry->fpDeltaPrevFrameSp_);
    SetFuncSize(funcEntry->funcSize_);
    SetCalleeRegisterNum(funcEntry->calleeRegisterNum_);
    SetCalleeReg2OffsetArray(funcEntry->CalleeReg2Offset_);
    
    return true;
}

bool MachineCode::SetData(const MachineCodeDesc &desc, JSHandle<Method> &method, size_t dataSize, bool buaaDebug)
{
    if (buaaDebug)
        printf("MachineCode::SetData start\n");
    DISALLOW_GARBAGE_COLLECTION;
    if (desc.codeType == MachineCodeType::BASELINE_CODE) {
        return SetBaselineCodeData(desc, method, dataSize);
    }

    SetOSROffset(MachineCode::INVALID_OSR_OFFSET);
    SetOsrDeoptFlag(false);
    SetOsrExecuteCnt(0);

    size_t instrSize = desc.rodataSizeBeforeTextAlign + desc.codeSizeAlign + desc.rodataSizeAfterTextAlign;

    SetInstructionsSize(instrSize);
    SetStackMapOrOffsetTableSize(desc.stackMapSizeAlign);
    SetPayLoadSizeInBytes(dataSize);
    
    size_t debugSectionSizeAlign = desc.debugInfoSizeAlign + desc.debugLineSizeAlign + desc.debugAbbrevSizeAlign +
                                   desc.debugStrSizeAlign;
    LOG_JIT(DEBUG) << "debugSectionSizeAlign:" << debugSectionSizeAlign;
    LOG_JIT(DEBUG) << "JitFort state:" << Jit::GetInstance()->IsEnableJitFort();
    if (Jit::GetInstance()->IsEnableJitFort()) {
        SetInstructionsAddr(desc.instructionsAddr);
        ASSERT(desc.instructionsAddr != 0);
        ASSERT(dataSize == (desc.funcEntryDesSizeAlign + desc.stackMapSizeAlign +
                            desc.symtabSizeAlign + debugSectionSizeAlign) ||
               dataSize == (desc.funcEntryDesSizeAlign + instrSize + desc.stackMapSizeAlign +
                            desc.symtabSizeAlign + debugSectionSizeAlign));
    } else {
        ASSERT(dataSize == (desc.funcEntryDesSizeAlign + instrSize + desc.stackMapSizeAlign +
                            desc.symtabSizeAlign + debugSectionSizeAlign));
    }
    if (!SetText(desc)) {
        return false;
    }
    if (!SetNonText(desc, method->GetMethodId())) {
        return false;
    }

    uint8_t *textStart = reinterpret_cast<uint8_t*>(GetText());
    CString methodName = method->GetRecordNameStr() + "." + CString(method->GetMethodName());
    LOG_JIT(DEBUG) << "Fast JIT MachineCode :" << methodName << ", "  << " text addr:" <<
        reinterpret_cast<void*>(GetText()) << ", size:" << instrSize;

    if (buaaDebug) {
        {
            std::fstream elfFile("jit_out.o", std::ios::in | std::ios::out |
                                                  std::ios::binary);
            if (!elfFile.is_open()) {
                std::cerr << "Failed to open ELF file!" << std::endl;
                return false;
            }

            // 读取 ELF 头
            llvm::ELF::Elf64_Ehdr ehdr;
            elfFile.seekg(0, std::ios::beg);
            elfFile.read(reinterpret_cast<char *>(&ehdr), sizeof(ehdr));

            // **读取 Section Header Table**
            elfFile.seekg(ehdr.e_shoff, std::ios::beg);
            std::vector<llvm::ELF::Elf64_Shdr> shdrs(ehdr.e_shnum);
            elfFile.read(reinterpret_cast<char *>(shdrs.data()),
                         ehdr.e_shnum * sizeof(llvm::ELF::Elf64_Shdr));

            // **读取节区字符串表（节区名称存储在 `shstrtab` 里）**
            llvm::ELF::Elf64_Shdr shstrtab = shdrs[ehdr.e_shstrndx];
            std::vector<char> strtab(shstrtab.sh_size);
            elfFile.seekg(shstrtab.sh_offset, std::ios::beg);
            elfFile.read(strtab.data(), shstrtab.sh_size);

            // **遍历段表，查找 `.text`**
            int textSecIndex = -1;
            for (size_t i = 0; i < ehdr.e_shnum; ++i) {
                std::string secName(&strtab[shdrs[i].sh_name]); // 获取段名
                if (secName == ".text") {
                    textSecIndex = i;
                    std::cout << "Found .text section:" << std::endl;
                    std::cout << "  Section Offset: 0x" << std::hex
                              << shdrs[i].sh_offset << std::endl;
                    std::cout << "  Section Virtual Address (sh_addr): 0x"
                              << std::hex << shdrs[i].sh_addr << std::endl;
                    std::cout << "  Section Size: 0x" << std::hex
                              << shdrs[i].sh_size << std::endl;
                    break;
                }
            }

            // **修改 .text 段的 sh_addr**
            llvm::ELF::Elf64_Off textSecOffset =
                ehdr.e_shoff + textSecIndex * sizeof(llvm::ELF::Elf64_Shdr) +
                offsetof(llvm::ELF::Elf64_Shdr, sh_addr);
            std::cout << "Modifying .text sh_addr at file offset: 0x"
                      << std::hex << textSecOffset << std::endl;

            uint64_t newAddr = reinterpret_cast<uint64_t>(textStart);
            elfFile.seekp(textSecOffset, std::ios::beg);
            elfFile.write(reinterpret_cast<char *>(&newAddr), sizeof(newAddr));

            std::cout << "Updated .text section sh_addr to: 0x" << std::hex
                      << newAddr << std::endl;

            elfFile.close();
        }
        {
            std::ifstream inFile("jit_out.o", std::ios::binary | std::ios::ate);

            const char *Buffer = nullptr;
            size_t Size = 0;

            if (inFile) {
                size_t fileSize = inFile.tellg();
                std::vector<char> *buffer =
                    new std::vector<char>(fileSize); // 动态分配，确保指针有效

                inFile.seekg(0, std::ios::beg);
                inFile.read(buffer->data(), fileSize);
                inFile.close();

                Buffer = buffer->data();
                Size = fileSize;
            } else {
                std::cerr << "Failed to open file for reading: " << "jit_out.o"
                          << std::endl;
            }

            jit_code_entry *JITCodeEntry = new jit_code_entry();

            if (!JITCodeEntry) {
                LOG_GHN(DEBUG)
                    << "Allocation failed when registering a JIT entry!";
            } else {
                JITCodeEntry->symfile_addr = Buffer;
                JITCodeEntry->symfile_size = Size;

                __jit_debug_descriptor.action_flag = JIT_REGISTER_FN;

                // Insert this entry at the head of the list.
                JITCodeEntry->prev_entry = nullptr;
                jit_code_entry *NextEntry = __jit_debug_descriptor.first_entry;
                JITCodeEntry->next_entry = NextEntry;
                if (NextEntry) {
                    NextEntry->prev_entry = JITCodeEntry;
                }
                __jit_debug_descriptor.first_entry = JITCodeEntry;
                __jit_debug_descriptor.relevant_entry = JITCodeEntry;
                __jit_debug_register_code();
            }
        }
    }

    if (buaaDebug)
        printf("MachineCode::SetData end\n");

    if (!SetPageProtect(textStart, dataSize)) {
        LOG_JIT(ERROR) << "MachineCode::SetData SetPageProtect failed";
        return false;
    }
    return true;
}

bool MachineCode::SetBaselineCodeData(const MachineCodeDesc &desc,
                                      JSHandle<Method> &method, size_t dataSize)
{
    DISALLOW_GARBAGE_COLLECTION;

    size_t instrSizeAlign = desc.codeSizeAlign;
    SetInstructionsSize(instrSizeAlign);

    SetStackMapOrOffsetTableSize(desc.stackMapSizeAlign);
    if (Jit::GetInstance()->IsEnableJitFort()) {
        ASSERT(desc.instructionsAddr != 0);
        ASSERT(dataSize == (desc.stackMapSizeAlign) ||  // reg. obj
               dataSize == (instrSizeAlign + desc.stackMapSizeAlign)); // huge obj
        SetInstructionsAddr(desc.instructionsAddr);
    } else {
        ASSERT(dataSize == (instrSizeAlign + desc.stackMapSizeAlign));
    }
    SetPayLoadSizeInBytes(dataSize);

    uint8_t *textStart = reinterpret_cast<uint8_t*>(GetText());
    if (Jit::GetInstance()->IsEnableJitFort()) {
        // relax assert for now until machine code padding for JitFort resolved
        ASSERT(IsAligned(reinterpret_cast<uintptr_t>(textStart), TEXT_ALIGN) ||
            IsAligned(reinterpret_cast<uintptr_t>(textStart), DATA_ALIGN));
    } else {
        ASSERT(IsAligned(reinterpret_cast<uintptr_t>(textStart), TEXT_ALIGN));
    }
    uint8_t *pText = textStart;

    if (!Jit::GetInstance()->IsEnableJitFort() || !Jit::GetInstance()->IsEnableAsyncCopyToFort() ||
        !desc.isAsyncCompileMode) {
        if (MachineCodeCopyToCache(desc, pText) == false) {
            return false;
        }
    }
    pText += instrSizeAlign;

    uint8_t *stackmapAddr = GetStackMapOrOffsetTableAddress();
    if (memcpy_s(stackmapAddr, desc.stackMapOrOffsetTableSize, // LCOV_EXCL_BR_LINE
                 reinterpret_cast<uint8_t*>(desc.stackMapOrOffsetTableAddr),
                 desc.stackMapOrOffsetTableSize) != EOK) {
        LOG_BASELINEJIT(ERROR) << "memcpy fail in copy fast baselineJIT offsetTable";
        return false;
    }

    SetFuncAddr(reinterpret_cast<uintptr_t>(textStart));

    CString methodName = method->GetRecordNameStr() + "." + CString(method->GetMethodName());
    LOG_BASELINEJIT(DEBUG) << "BaselineCode :" << methodName << ", "  << " text addr:" <<
        reinterpret_cast<void*>(GetText()) << ", size:" << instrSizeAlign  <<
        ", stackMap addr: 0, size: 0";

    if (!SetPageProtect(textStart, dataSize)) {
        LOG_BASELINEJIT(ERROR) << "MachineCode::SetBaseLineCodeData SetPageProtect failed";
        return false;
    }
    return true;
}

bool MachineCode::IsInText(const uintptr_t pc) const
{
    uintptr_t textStart = GetText();
    uintptr_t textEnd = textStart + GetTextSize();
    return textStart <= pc && pc < textEnd;
}

std::tuple<uint64_t, uint8_t*, int, kungfu::CalleeRegAndOffsetVec> MachineCode::CalCallSiteInfo() const
{
    uintptr_t textStart = GetText();
    uint8_t *stackmapAddr = GetStackMapOrOffsetTableAddress();
    ASSERT(stackmapAddr != nullptr);

    int delta = GetFpDeltaPrevFrameSp();
    kungfu::CalleeRegAndOffsetVec calleeRegInfo;
    for (uint32_t j = 0; j < GetCalleeRegisterNum(); j++) {
        kungfu::LLVMStackMapType::DwarfRegType reg =
            static_cast<kungfu::LLVMStackMapType::DwarfRegType>(GetCalleeReg2OffsetArray(2 * j));
        kungfu::LLVMStackMapType::OffsetType offset =
            static_cast<kungfu::LLVMStackMapType::OffsetType>(GetCalleeReg2OffsetArray(2 * j + 1));
        kungfu::LLVMStackMapType::DwarfRegAndOffsetType regAndOffset = std::make_pair(reg, offset);
        calleeRegInfo.emplace_back(regAndOffset);
    }
    auto ret = std::make_tuple(textStart, stackmapAddr, delta, calleeRegInfo);
    return ret;
}

uintptr_t MachineCode::GetText() const
{
    if (Jit::GetInstance()->IsEnableJitFort()) {
        return GetInstructionsAddr();
    } else {
        return GetNonTextAddress();
    }
}

uint8_t *MachineCode::GetStackMapOrOffsetTableAddress() const
{
    if (Jit::GetInstance()->IsEnableJitFort()) {
        // stackmap immediately follows MachineCode NonText area
        return reinterpret_cast<uint8_t*>(GetNonTextAddress());
    } else {
        return reinterpret_cast<uint8_t*>(GetText() + GetInstructionsSize());
    }
}

void MachineCode::ProcessMarkObject()
{
    Region *region = Region::ObjectAddressToRange(this);
    Heap *localHeap = reinterpret_cast<Heap *>(region->GetLocalHeap());
    if (!localHeap) {
        // it is a huge machine code object. skip
        return;
    }
    ASSERT(localHeap->GetMachineCodeSpace());
    if (localHeap->GetMachineCodeSpace()) {
        localHeap->GetMachineCodeSpace()->MarkJitFortMemAlive(this);
    }
}

}  // namespace panda::ecmascript
